Ruby 日記 40日目: Fiberとコンテキストの切り替え
次のプログラムで実行した期待値を得られるように、__(1)__と__(2)__のメソッドの組み合わせを選んでください。
code:rb
fiber = Fiber.new do
__(1)__ 'Hi, there!'
end
p __(2)__
table:選択肢:
番号 __(1)__ __(2)__
1. Fiber.yield fiber.resume
2. Fiber.resume fiber.yield
3. fiber.resume Fiber.yield
4. fiber.yield Fiber.resume
解説:
ノンプリエンプティブな軽量スレッド(以下ファイバーと呼ぶ)を提供します。 他の言語では coroutine あるいは semicoroutine と呼ばれることもあります。
Thread と違いユーザレベルスレッドとして実装されています。Thread クラスが表すスレッドと違い、明示的に指定しない限り ファイバーのコンテキストは切り替わりません。
Threadとの違いが述べられている。Threadもよくわかってないので別途やろうな
またファイバーは親子関係を持ちます。Fiber#resume を呼んだファイバーが親になり 呼ばれたファイバーが子になります。
なるほど親子関係があるのね。
親子関係を壊すような遷移(例えば 自分の親の親のファイバーへ切り替えるような処理)はできません。 例外 FiberError が発生します。
ふむふむ
できることは
- Fiber#resume により子へコンテキストを切り替える
- Fiber.yield により親へコンテキストを切り替える
の二通りです。この親子関係は一時的なものであり 親ファイバーへコンテキストを切り替えた時点で解消されます。
ふーん
resumeがインスタンスメソッドでyieldがクラスメソッドなのはわかった
ファイバーは処理のあるポイントで他のルーチンにコンテキストを切り替え、またそのポイントから再開する という目的のために使います。
なるほどわかりやすい
Fiber.new により与えられたブロックとともにファイバーを生成します。
生成したファイバーに対して Fiber#resume を呼ぶことによりコンテキストを切り替えます。
子ファイバーのブロック中で Fiber.yield を呼ぶと親にコンテキストを切り替えます。
Fiber.yield の引数が、親での Fiber#resume の返り値になります。
code:rb
f = Fiber.new do
n = 0
loop do
Fiber.yield(n)
n += 1
end
end
5.times do
p f.resume
end
1
2
3
4
は〜んなるほどね!
普通に
code:rb
n = 0
loop do
n += 1
end
なんてやると無限ループするわけだけれど、
Fiber.new したブロックの中で Fiber.yield をすることで
code:rb
f = Fiber.new do
n = 0
loop do
Fiber.yield(n)
n += 1
end
end
親のコンテキスト(ここではトップレベルのコンテキスト)に切り替わるわけだ。
で、resumeメソッドで子のコンテキスト(Fiber.newしたブロックのなか)に切り替わるってことね。
code:rb
5.times do
p f.resume
end
ちなみにresumeには「再開する」って意味がある
以下は内部イテレータを外部イテレータに変換する例です。 実際 Enumerator は Fiber を用いて実装されています。
なるほど〜、そうだったのか〜
code:rb
def enum2gen(enum)
Fiber.new do
enum.each{|i|
Fiber.yield(i)
}
end
end
g = enum2gen(1..100)
さて、問題に戻ると、答えは「__(1)__ = Fiber.yield, __(2)__ = fiber.resume」ってことになるね!
code:sh
# ruby gold/ex40/choice01.rb
"Hi, there!"
# ruby gold/ex40/choice02.rb
# ruby gold/ex40/choice03.rb
gold/ex40/choice03.rb:5:in `yield': can't yield from root fiber (FiberError)
from gold/ex40/choice03.rb:5:in `<main>'
# ruby gold/ex40/choice04.rb
gold/ex40/choice04.rb:5:in <main>': undefined method resume' for Fiber:Class (NoMethodError)